/**************************************************************************************
* Projection.java
* 
* Something that acts over a series of cells. Projections include spell effects, 
* missiles(fired or thrown), etc
* 
* Projections are created, then are run using the project() function to run the next step of the projection.
* project() affects the next cell(s) in the path and applies a projection sprite to the cells affected.
* 
* Projection is meant to be used by Main class iteratively, allowing Mythos to redraw the screen and wait
* for proscribed amount of time before projecting again.
* 
*************************************************************************************/
package creid.mythos.engine;

public class Projection
{
	//Constants/////////////////////////////////////////////////////////////////////////
	
	//Projection shapes
	public static final int SELF = -1; 
	public static final int BOLT = 0; 
	public static final int BALL = 1; //Just a bolt with radius
	public static final int BEAM = 2;  //Just a bolt with negative radius, radius being N-in-5 chance of piercing, so -5 = always pierce
	public static final int SMITE = 3; //No start cell, just end and radius
	public static final int RADIAL = 4; //No end cell, just start and radius.
	public static final int CONE = 5; //No radius.
	
	public static final int ALWAYS_PIERCE = -5;
	
	public static final int DODGE_ROLL = 20;
	public static final int DODGE_CAP = 19;	//Always 5% chance to be hit, even with super-duper dodginess

	public static final int STANDARD_RANGE = GameMap.VIEW_DISTANCE - 1;	//Can see mobs slightly before being in range

	//Attributes////////////////////////////////////////////////////////////////////////
	
	private int type;
	
	//Thing being projected
	private Item projectile;
	
	private String message;
	
	private Cell start;
	private Cell end;
	
	private int range;
	
	private Cell[][] template; //cells to be projected over
	private int step;
	
	private int radius;
	
	private boolean seen;
	boolean whiff;	//If projection hits nothing; used to drop projectiles at end of path
	
	//Constructors//////////////////////////////////////////////////////////////////////

	public Projection(Item projectile, int type, Cell start, Cell end, int radius, int range, String message)
	{
		this.projectile = projectile;
		this.type = type;
		this.start = start;
		this.end = end;
		this.radius = radius;
		this.range = range;
		this.setMessage(message);
		seen = false;		
		buildTemplate();
		step = 0;
		whiff = true;
	}

	//Methods///////////////////////////////////////////////////////////////////////////

	//Creator helpers
	
	//Selfie
	public static Projection selfProjection(Item projectile, Cell affected, String message)
	{
		return new Projection(projectile, SELF, affected, null, 0, 1, message);
	}
	
	//Bolts
	public static Projection boltProjection(Item projectile, Cell start, Cell end, String message)
	{
		//Chance of projectile to survive being shot
		int land = 0;
		if (projectile.hasProperty("land"))
		{
			land = projectile.getProperty("land");
		}
		
		return new Projection(projectile, BOLT, start, end, land, STANDARD_RANGE, message);
	}
	
	//Balls
	public static Projection ballProjection(Item projectile, Cell start, Cell end, int radius, String message)
	{
		return new Projection(projectile, BALL, start, end, radius, STANDARD_RANGE, message);
	}
	
	//Beams
	public static Projection beamProjection(Item projectile, Cell start, Cell end, String message)
	{
		return new Projection(projectile, BEAM, start, end, ALWAYS_PIERCE, STANDARD_RANGE, message);
	}
	
	//Smites
	public static Projection smiteProjection(Item projectile, Cell target, int radius, String message)
	{
		return new Projection(projectile, SMITE, null, target, radius, STANDARD_RANGE, message);
	}
	
	//Radials
	public static Projection radialProjection(Item projectile, Cell start, int radius, int range, String message)
	{
		return new Projection(projectile, RADIAL, start, null, radius, STANDARD_RANGE, message);
	}
	
	//Cones
	public static Projection coneProjection(Item projectile, Cell start, Cell end, String message)
	{
		return new Projection(projectile, CONE, start, end, 0, STANDARD_RANGE, message);
	}

	//Getters and setters

	public int getType()
	{
		return type;
	}
	
	public Item getProjectile()
	{
		return projectile;
	}

	public void setProjectile(Item projectile)
	{
		this.projectile = projectile;
	}

	public Cell getStart()
	{
		return start;
	}
	
	public boolean isSeen()
	{
		return seen;
	}

	public void setStart(Cell start)
	{
		this.start = start;
	}

	public Cell getEnd()
	{
		return end;
	}

	public void setEnd(Cell end)
	{
		this.end = end;
	}

	public String getMessage()
	{
		return message;
	}

	public void setMessage(String message)
	{
		this.message = message;
	}

	public int getRadius()
	{
		return radius;
	}

	public void setRadius(int radius)
	{
		this.radius = radius;
	}
	
	public boolean canProject()
	{
		if (step < template.length)
			return true;
		return false;
	}
	
	/**
	 * Build the array of cells to be operated on
	 */
	private void buildTemplate()
	{
		GameMap map;
		
		if (start == null)
			if (end == null)
				return;
			else
				map = end.getMap();
		else
			map = start.getMap();
		
		switch(type)
		{
		case SELF:
			//Just start cell
			template = new Cell[1][1];
			template[0][0] = start;
			break;
			
		case BOLT:
			//Just a line
			Cell[] temp = map.getLine(start, end);
			
			//How far will the bolt go before it hits a wall
			//Note that we don't stop on first actor because bolt can miss
			int max = 0;
			for (int i = 1; i < temp.length; i++)
			{
				max++;
				if (!temp[i].isSolid())
					break;
			}
			
			//Limit by our given range
			if (max > range)
				max = range;
			
			//Skip the projectors' cell so we don't hit them
			template = new Cell[max][1];
			for (int i = 0; i < max; i++)
			{
				template[i][0] = temp[i+1];
			}
			break;
			
		case BALL:
		case BEAM:
		case SMITE:
		case RADIAL:
		case CONE:
		}
		
		//Check to see if the projection is visible
		for (Cell[] stepCells : template)
		{
			if (seen)
				break;

			for (Cell stepCell : stepCells)
			{
				if (stepCell.isSeen())
				{
					seen = true;
					break;
				}
			}
		}
	}
	
	/**
	 * check if actor in cell c dodged the projectile
	 * @param c
	 * @return
	 */
	private boolean dodged(Actor a)
	{
		//Doesn't make sense to dodge our own effects
		if (type == SELF)
			return false;
		
		//Can't hit what isn't there
		if (a == null)
			return true;
		
		int chance = 0; 
		
		//Easier for smaller creatures to dodge, harder for big ones
		if (a.getSize() == Actor.SMALL)
			chance += 3;
		else if (a.getSize() == Actor.LARGE)
			chance -= 3;
		
		if (!a.isEncumbered())
			chance += a.getGrace();
		
		//TODO: Increase chance is occupant moved on their turn
		
		//Can never be 100% immune, though being ghostlike can get you close
		if (chance > DODGE_CAP || a.hasProperty("insubstantial"))
			chance = DODGE_CAP;
		
		//Check to see if projectile is dodged
		if (Mythos.callRNG(1, DODGE_ROLL) <= chance)
			return true;
		else
			return false;
	}
	
	/**
	 * projectOntoActor
	 * See if the projectile affects the actor or misses them
	 * @param target
	 * @return hit (true) or miss
	 */
	private boolean projectOntoActor(Actor target)
	{
		boolean hit = true;
		
		//Can the target dodge it?
		//See if creature avoid projection
		if (dodged(target))
		{
			Mythos.logger.logMessage(target.getName(Entity.DEFINITE) + " dodges " + projectile.getName(Entity.DEFINITE) + "!");
			hit = false;
		}
		
		if (projectile.getElement() == Entity.HEAL)
		{
			//TODO: Should hurt undead
			target.heal(projectile.getPower());
			
			hit = false;
		}
		
		//Attempt to hit target with the projectile
		if (hit)
		{
			StringBuilder projectMessage = new StringBuilder(projectile.getName(Entity.DEFINITE) + " hits " + target.getName(Entity.DEFINITE));
			
			int attack = projectile.getProperty("throw");
			int defense = target.getDefense(projectile.getElement());
			
			int roll = Mythos.callRNG(1, attack + defense);
			
			//Hit! A palpable hit!
			if (roll <= attack)
			{
				projectMessage.append(" for " + roll + " damage");
				target.takeHit("was killed by " + projectile.getName(Entity.INDEFINITE), roll);
			}
			else
			{
				hit = false;
				projectMessage.append(" but does no damage!");
			}
			
			//Write home about it
			if (target.getLocation().isSeen())
				Mythos.logger.logMessage(projectMessage.toString());
		}
		
		return hit;
	}
	
	/**
	 * projectOnCell
	 * Project onto a cell
	 * Return if the projection hits something
	 * @param cell
	 * @return
	 */
	private boolean projectOntoCell(Cell cell)
	{
		boolean success = false;
		
		if (cell.isSeen())
			cell.setAnimation(projectile.getSprite());
		
		if (cell.isOccupied())
		{
			if (projectOntoActor(cell.getOccupant()))
			{
				whiff = false;
				success = true;
				
				//If the projectile can survive hitting the target, drop it on the ground
				//If it fails, it fails. We don't care.
				if (projectile.hasProperty("land") && Mythos.callRNG(1, 5) <= projectile.getProperty("land"))
					cell.getMap().placeItem(projectile, cell.getX(), cell.getY(), true);
				else
					Mythos.logger.logMessage(projectile.getName(Entity.DEFINITE) + " breaks!");
			}
		}
		
		return success;
	}
	
	/**
	 * project: Do the next step in a projection
	 */
	public void project()
	{
		boolean stopped = false;
		
		//Clean up old animation
		if (type != BEAM && step > 0)
		{
			for (Cell old: template[step - 1])
				old.setAnimation(null);
		}
		
		for(Cell c: template[step])
			if (projectOntoCell(c))
				stopped = true;
		
		//Don't project any more
		if (stopped)
			step = template.length;
		else
			step++;
	}

	public int getRange()
	{
		return range;
	}

	public void setRange(int range)
	{
		this.range = range;
	}
	
	/**
	 * cleanup
	 * Remove any lingering projections
	 */
	public void cleanup()
	{
		for (Cell[] stepCells: template)
			for (Cell c: stepCells)
				c.setAnimation(null);
		
		//Drop item on last place
		if (whiff && projectile.hasProperty("land"))
		{
			int i = template.length - 1;
			
			String breakMessage = projectile.getName(Entity.DEFINITE) + " breaks!";
			
			Cell last = template[i][0];
			while (last.isSolid() && i >= 0)
			{
				last = template[i][0];
				i--;
			}
			
			if (!last.isSolid())
			{
				if (Mythos.callRNG(1, 5) > projectile.getProperty("land"))
					Mythos.logger.logMessage(breakMessage);
				else //the item lands safely
					last.getMap().placeItem(projectile, last.getX(), last.getY(), true);
			}
			else Mythos.logger.logMessage(breakMessage);
				
		}
	}
}
